﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConnectionForm.cs" company="">
//   
// </copyright>
// <summary>
//   Defines the ConnectionForm type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ComparisonTool.Forms.Connection
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Windows.Forms;

    using ComparisonTool.Connection;

    using Microsoft.Xrm.Client;
    using Microsoft.Xrm.Client.Services;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Sdk.Discovery;

    /// <summary>
    /// The connection form.
    /// </summary>
    public partial class ConnectionForm : Form
    {
        /// <summary>
        /// The organizations.
        /// </summary>
        private List<OrganizationDetail> organizations;

        /// <summary>
        /// The do connect.
        /// </summary>
        private bool doConnect;

        /// <summary>
        /// Gets or sets the connection list.
        /// </summary>
        public CrmConnections ConnectionList { get; set; }

        /// <summary>
        /// Gets a value indicating whether do connect.
        /// </summary>
        public bool DoConnect
        {
            get { return this.doConnect; }
        }

        /// <summary>
        /// Gets or sets the connection detail.
        /// </summary>
        public ConnectionDetail CrmConnectionDetail { get; set; }

        /// <summary>
        /// The is creation mode.
        /// </summary>
        private readonly bool isCreationMode;

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionForm"/> class.
        /// </summary>
        /// <param name="isCreation">
        /// The is creation.
        /// </param>
        public ConnectionForm(bool isCreation)
        {
            this.InitializeComponent();
            this.isCreationMode = isCreation;
            this.cbbOnlineEnv.SelectedIndex = 0;

            var tip = new ToolTip { ToolTipTitle = "Information" };
            tip.SetToolTip(this.tbServerName, "For CRM Online or OSDP, use:\r\ncrm.dynamics.com for North America\r\ncrm4.dynamics.com for EMEA\r\ncrm5.dynamics.com for Asia Pacific\r\n\r\nFor OnPremise:\r\nUse the server name\r\n\r\nFor IFD:\r\nUse <organization_name>.<domain>.<extension>");
            tip.SetToolTip(this.tbServerPort, "Specify port only if different from 80 or 443 (SSL)");
            tip.SetToolTip(this.tbHomeRealmUrl, "(Optional) In specific case, you should need to specify the home realm url to authenticate through ADFS");

            ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
        }

        /// <summary>
        /// The validate remote certificate.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="certificate">
        /// The certificate.
        /// </param>
        /// <param name="chain">
        /// The chain.
        /// </param>
        /// <param name="policyErrors">
        /// The policy errors.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private static bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors)
        {
            return true;
        }

        /// <summary>
        /// The on load.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnLoad(EventArgs e)
        {
            if (this.CrmConnectionDetail != null)
            {
                this.FillValues();
            }

            base.OnLoad(e);
        }

        /// <summary>
        /// The b validate click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void BValidateClick(object sender, EventArgs e)
        {
            if (this.tbName.Text.Length == 0)
            {
                MessageBox.Show(this, @"You must define a name for this connection!", @"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (this.comboBoxOrganizations.Text.Length == 0 && this.comboBoxOrganizations.SelectedItem == null && !(this.cbUseIfd.Checked || this.cbUseOSDP.Checked))
            {
                MessageBox.Show(this, @"You must select an organization!", @"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (this.tbUserPassword.Text.Length == 0 && (this.cbUseIfd.Checked || this.rbAuthenticationCustom.Checked))
            {
                MessageBox.Show(this, @"You must define a password!", @"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (this.CrmConnectionDetail == null)
            {
                this.CrmConnectionDetail = new ConnectionDetail();
            }

            // Save connection details in structure
            this.CrmConnectionDetail.ConnectionName = this.tbName.Text;
            this.CrmConnectionDetail.IsCustomAuth = this.rbAuthenticationCustom.Checked;
            this.CrmConnectionDetail.UseSsl = this.cbUseSsl.Checked;
            this.CrmConnectionDetail.UseOnline = this.cbUseOnline.Checked;
            this.CrmConnectionDetail.UseOsdp = this.cbUseOSDP.Checked;
            this.CrmConnectionDetail.ServerName = (this.cbUseOSDP.Checked || this.cbUseOnline.Checked) ? this.cbbOnlineEnv.SelectedItem.ToString() : this.tbServerName.Text;
            this.CrmConnectionDetail.ServerPort = this.tbServerPort.Text;
            this.CrmConnectionDetail.UserDomain = this.tbUserDomain.Text;
            this.CrmConnectionDetail.UserName = this.tbUserLogin.Text;
            this.CrmConnectionDetail.UserPassword = this.tbUserPassword.Text;
            this.CrmConnectionDetail.SavePassword = this.chkSavePassword.Checked;
            this.CrmConnectionDetail.UseIfd = this.cbUseIfd.Checked;
            this.CrmConnectionDetail.HomeRealmUrl = this.tbHomeRealmUrl.Text.Length > 0 ? this.tbHomeRealmUrl.Text : null;

            try
            {
                this.FillDetails();

                if (MessageBox.Show(this, @"Do you want to connect now to this server?", @"Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    this.doConnect = true;
                }

                this.DialogResult = DialogResult.OK;
                this.Close();
            }
            catch (Exception error)
            {
                MessageBox.Show(this, error.Message, @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// The b cancel click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void BCancelClick(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }

        /// <summary>
        /// The combo box organizations selected index changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ComboBoxOrganizationsSelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.comboBoxOrganizations.Text.Length > 0 || this.comboBoxOrganizations.SelectedItem != null)
            {
                this.bValidate.Enabled = true;
            }
            else
            {
                this.bValidate.Enabled = false;
            }
        }

        /// <summary>
        /// The combo box organizations text changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ComboBoxOrganizationsTextChanged(object sender, EventArgs e)
        {
            if (this.comboBoxOrganizations.Text.Length > 0 || this.comboBoxOrganizations.SelectedItem != null)
            {
                this.bValidate.Enabled = true;
            }
            else
            {
                this.bValidate.Enabled = false;
            }
        }

        /// <summary>
        /// The cb use ifd checked changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void CbUseIfdCheckedChanged(object sender, EventArgs e)
        {
            if (this.cbUseIfd.Checked)
            {
                this.cbUseOnline.Checked = false;
                this.cbUseOSDP.Checked = false;
            }

            this.bValidate.Enabled = this.cbUseIfd.Checked;

            this.rbAuthenticationCustom.Checked = this.cbUseIfd.Checked;
            this.rbAuthenticationIntegrated.Enabled = !this.cbUseIfd.Checked;
            this.rbAuthenticationIntegrated.Checked = !this.cbUseIfd.Checked;

            this.tbUserDomain.Enabled = this.cbUseIfd.Checked;
            this.tbUserLogin.Enabled = this.cbUseIfd.Checked;
            this.tbUserPassword.Enabled = this.cbUseIfd.Checked;

            this.tbServerName.Visible = this.cbUseIfd.Checked;
            this.cbbOnlineEnv.Visible = !this.cbUseIfd.Checked;
            this.tbHomeRealmUrl.Enabled = this.cbUseIfd.Checked;

            this.cbUseSsl.Checked = this.cbUseIfd.Checked;
            this.cbUseSsl.Enabled = !this.cbUseIfd.Checked;
        }

        /// <summary>
        /// The cb use online checked changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void CbUseOnlineCheckedChanged(object sender, EventArgs e)
        {
            if (this.cbUseOnline.Checked)
            {
                this.cbUseIfd.Checked = false;

                this.rbAuthenticationCustom.Checked = true;
                this.rbAuthenticationIntegrated.Enabled = false;
                this.rbAuthenticationIntegrated.Checked = false;

                this.tbUserDomain.Text = string.Empty;

                this.tbUserDomain.Enabled = false;
                this.tbUserLogin.Enabled = true;
                this.tbUserPassword.Enabled = true;

                this.tbServerName.Visible = !this.cbUseOnline.Checked;
                this.cbbOnlineEnv.Visible = this.cbUseOnline.Checked;
                this.tbServerPort.Text = string.Empty;
                this.tbHomeRealmUrl.Text = string.Empty;

                this.cbUseSsl.Checked = true;
                this.cbUseSsl.Enabled = false;
                this.tbServerPort.Enabled = false;
                this.tbHomeRealmUrl.Enabled = false;
            }
            else
            {
                this.rbAuthenticationCustom.Checked = false;
                this.rbAuthenticationIntegrated.Enabled = true;
                this.rbAuthenticationIntegrated.Checked = true;

                this.tbUserDomain.Enabled = false;
                this.tbUserLogin.Enabled = false;
                this.tbUserPassword.Enabled = false;

                this.cbUseSsl.Checked = false;
                this.cbUseSsl.Enabled = true;
                this.tbServerPort.Enabled = true;
            }
        }

        /// <summary>
        /// The cb use osdp checked changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void CbUseOsdpCheckedChanged(object sender, EventArgs e)
        {
            if (this.cbUseOSDP.Checked)
            {
                this.cbUseIfd.Checked = false;
                this.cbUseOnline.Checked = true;
                this.cbUseOnline.Enabled = false;

                this.rbAuthenticationCustom.Checked = true;
                this.rbAuthenticationIntegrated.Enabled = false;
                this.rbAuthenticationIntegrated.Checked = false;

                this.tbUserDomain.Text = string.Empty;

                this.tbUserDomain.Enabled = false;
                this.tbUserLogin.Enabled = true;
                this.tbUserPassword.Enabled = true;

                this.tbServerName.Visible = !this.cbUseOnline.Checked;
                this.cbbOnlineEnv.Visible = this.cbUseOnline.Checked;
                this.tbServerPort.Text = string.Empty;
                this.tbHomeRealmUrl.Text = string.Empty;

                this.cbUseSsl.Checked = true;
                this.cbUseSsl.Enabled = false;
                this.tbServerPort.Enabled = false;
                this.tbHomeRealmUrl.Enabled = false;
            }
            else
            {
                this.rbAuthenticationCustom.Checked = this.cbUseOnline.Checked;
                this.rbAuthenticationIntegrated.Enabled = !this.cbUseOnline.Checked;
                this.cbUseOnline.Enabled = true;

                this.tbUserDomain.Enabled = false;
                this.tbUserLogin.Enabled = this.cbUseOnline.Checked;
                this.tbUserPassword.Enabled = this.cbUseOnline.Checked;

                this.cbUseSsl.Checked = this.cbUseOnline.Checked;
                this.cbUseSsl.Enabled = !this.cbUseOnline.Checked;
                this.tbServerPort.Enabled = !this.cbUseOnline.Checked;
            }
        }

        /// <summary>
        /// The rb authentication integrated checked changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void RbAuthenticationIntegratedCheckedChanged(object sender, EventArgs e)
        {
            if (this.rbAuthenticationIntegrated.Checked)
            {
                this.tbUserDomain.Text = string.Empty;
                this.tbUserLogin.Text = string.Empty;
                this.tbUserPassword.Text = string.Empty;
            }

            this.tbUserDomain.Enabled = this.rbAuthenticationCustom.Checked;
            this.tbUserLogin.Enabled = this.rbAuthenticationCustom.Checked;
            this.tbUserPassword.Enabled = this.rbAuthenticationCustom.Checked;
        }

        /// <summary>
        /// The b get organizations click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void BGetOrganizationsClick(object sender, EventArgs e)
        {
            var warningMessage = string.Empty;
            var goodAuthenticationData = false;
            var goodServerData = false;

            // Check data filled by user
            if (this.rbAuthenticationIntegrated.Checked
                || (this.rbAuthenticationCustom.Checked
                    && (this.tbUserDomain.Text.Length > 0 || this.cbUseIfd.Checked || this.cbUseOSDP.Checked) &&
                this.tbUserLogin.Text.Length > 0 && this.tbUserPassword.Text.Length > 0)
                || (this.cbUseOnline.Checked && !string.IsNullOrEmpty(this.tbUserLogin.Text)
                    && !string.IsNullOrEmpty(this.tbUserPassword.Text))) 
                goodAuthenticationData = true;

            if (this.tbServerName.Text.Length > 0 || this.cbbOnlineEnv.SelectedIndex >= 0)
            {
                goodServerData = true;
            }

            if (!goodServerData)
            {
                warningMessage += "Please provide server name\r\n";
            }

            if (!goodAuthenticationData)
            {
                warningMessage += "Please fill all authentication textboxes\r\n";
            }

            if (warningMessage.Length > 0)
            {
                MessageBox.Show(this, warningMessage, @"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else
            {
                // Save connection details in structure
                if (this.isCreationMode)
                {
                    this.CrmConnectionDetail = new ConnectionDetail();
                }

                this.CrmConnectionDetail.ConnectionName = this.tbName.Text;
                this.CrmConnectionDetail.IsCustomAuth = this.rbAuthenticationCustom.Checked;
                this.CrmConnectionDetail.UseSsl = this.cbUseSsl.Checked;
                this.CrmConnectionDetail.ServerName = (this.cbUseOSDP.Checked || this.cbUseOnline.Checked) ? this.cbbOnlineEnv.SelectedItem.ToString() : this.tbServerName.Text;
                this.CrmConnectionDetail.ServerPort = this.tbServerPort.Text;
                this.CrmConnectionDetail.UserDomain = this.tbUserDomain.Text;
                this.CrmConnectionDetail.UserName = this.tbUserLogin.Text;
                this.CrmConnectionDetail.UserPassword = this.tbUserPassword.Text;
                this.CrmConnectionDetail.UseIfd = this.cbUseIfd.Checked;
                this.CrmConnectionDetail.UseOnline = this.cbUseOnline.Checked;
                this.CrmConnectionDetail.UseOsdp = this.cbUseOSDP.Checked;
                this.CrmConnectionDetail.HomeRealmUrl = this.tbHomeRealmUrl.Text.Length > 0 ? this.tbHomeRealmUrl.Text : null;

                this.CrmConnectionDetail.AuthType = AuthenticationProviderType.ActiveDirectory;
                if (this.cbUseIfd.Checked)
                {
                    this.CrmConnectionDetail.AuthType = AuthenticationProviderType.Federation;
                }
                else if (this.cbUseOSDP.Checked)
                {
                    this.CrmConnectionDetail.AuthType = AuthenticationProviderType.OnlineFederation;
                }
                else if (this.cbUseOnline.Checked)
                {
                    this.CrmConnectionDetail.AuthType = AuthenticationProviderType.LiveId;
                }

                // Launch organization retrieval
                this.comboBoxOrganizations.Items.Clear();
                this.organizations = new List<OrganizationDetail>();
                this.Cursor = Cursors.WaitCursor;

                var bw = new BackgroundWorker();
                bw.DoWork += this.BwDoWork;
                bw.RunWorkerCompleted += this.BwRunWorkerCompleted;
                bw.RunWorkerAsync();
            }
        }

        /// <summary>
        /// The bw do work.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void BwDoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = this.RetrieveOrganizations(this.CrmConnectionDetail);
        }

        /// <summary>
        /// The bw run worker completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(this, @"An error occured while retrieving organizations: " + e.Error.Message, @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                foreach (OrganizationDetail orgDetail in (OrganizationDetailCollection)e.Result)
                {
                    this.organizations.Add(orgDetail);

                    this.comboBoxOrganizations.Items.Add(new Organization { OrganizationDetail = orgDetail });
                    this.comboBoxOrganizations.SelectedIndex = 0;
                    this.comboBoxOrganizations.Enabled = true;
                }
            }

            this.Cursor = Cursors.Default;
        }

        /// <summary>
        /// The retrieve organizations.
        /// </summary>
        /// <param name="currentDetail">
        /// The current detail.
        /// </param>
        /// <returns>
        /// The <see cref="OrganizationDetailCollection"/>.
        /// </returns>
        private OrganizationDetailCollection RetrieveOrganizations(ConnectionDetail currentDetail)
        {
            WebRequest.GetSystemWebProxy();

            var connection = CrmConnection.Parse(currentDetail.GetDiscoveryCrmConnectionString());
            var service = new DiscoveryService(connection);

            var request = new RetrieveOrganizationsRequest();
            var response = (RetrieveOrganizationsResponse)service.Execute(request);
            return response.Details;
        }

        /// <summary>
        /// The fill values.
        /// </summary>
        private void FillValues()
        {
            this.rbAuthenticationCustom.Checked = this.CrmConnectionDetail.IsCustomAuth;
            this.rbAuthenticationIntegrated.Checked = !this.CrmConnectionDetail.IsCustomAuth;

            // rbAuthenticationIntegrated.CheckedChanged += new EventHandler(rbAuthenticationIntegrated_CheckedChanged);
            this.tbName.Text = this.CrmConnectionDetail.ConnectionName;
            this.tbServerPort.Text = this.CrmConnectionDetail.ServerPort;
            this.tbUserDomain.Text = this.CrmConnectionDetail.UserDomain;
            this.tbUserLogin.Text = this.CrmConnectionDetail.UserName;
            this.tbUserPassword.Text = this.CrmConnectionDetail.UserPassword;
            this.chkSavePassword.Checked = this.CrmConnectionDetail.SavePassword;
            this.comboBoxOrganizations.Text = this.CrmConnectionDetail.OrganizationFriendlyName;
            this.cbUseIfd.Checked = this.CrmConnectionDetail.UseIfd;
            this.cbUseOSDP.Checked = this.CrmConnectionDetail.UseOsdp;
            this.cbUseOnline.Checked = this.CrmConnectionDetail.UseOnline;
            this.cbUseSsl.Checked = this.CrmConnectionDetail.UseSsl;
            this.tbHomeRealmUrl.Text = this.CrmConnectionDetail.HomeRealmUrl;

            this.tbHomeRealmUrl.Enabled = this.CrmConnectionDetail.UseIfd;

            if (this.CrmConnectionDetail.UseOnline || this.CrmConnectionDetail.UseOsdp)
            {
                this.tbServerName.Visible = false;
                this.cbbOnlineEnv.Visible = true;

                this.cbbOnlineEnv.SelectedItem = this.CrmConnectionDetail.ServerName;
            }
            else
            {
                this.tbServerName.Visible = true;
                this.cbbOnlineEnv.Visible = false;

                this.tbServerName.Text = this.CrmConnectionDetail.ServerName;
            }
        }

        /// <summary>
        /// The fill details.
        /// </summary>
        /// <exception cref="Exception">
        /// </exception>
        private void FillDetails()
        {
            bool hasFoundOrg = false;

            var selectedOrganization = this.comboBoxOrganizations.SelectedItem != null ? ((Organization)this.comboBoxOrganizations.SelectedItem).OrganizationDetail : null;

            if (this.organizations == null || this.organizations.Count == 0)
            {
                var orgs = this.RetrieveOrganizations(this.CrmConnectionDetail);
                foreach (var orgDetail in orgs)
                {
                    if (this.organizations == null)
                    {
                        this.organizations = new List<OrganizationDetail>();
                    }

                    this.organizations.Add(orgDetail);

                    this.comboBoxOrganizations.Items.Add(new Organization { OrganizationDetail = orgDetail });
                    this.comboBoxOrganizations.SelectedIndex = 0;
                    this.comboBoxOrganizations.Enabled = true;
                }
            }

            if (this.organizations == null)
            {
                MessageBox.Show(this, @"Organizations list is empty!", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            foreach (var organization in this.organizations)
            {
                if (selectedOrganization != null && organization.UniqueName == selectedOrganization.UniqueName)
                {
                    this.CrmConnectionDetail.OrganizationServiceUrl = organization.Endpoints[EndpointType.OrganizationService];
                    this.CrmConnectionDetail.Organization = organization.UniqueName;
                    this.CrmConnectionDetail.OrganizationUrlName = organization.UrlName;
                    this.CrmConnectionDetail.OrganizationFriendlyName = organization.FriendlyName;
                    this.CrmConnectionDetail.OrganizationVersion = organization.OrganizationVersion;

                    this.CrmConnectionDetail.ConnectionName = this.tbName.Text;

                    if (this.isCreationMode)
                    {
                        this.CrmConnectionDetail.ConnectionId = Guid.NewGuid();
                    }

                    hasFoundOrg = true;

                    break;
                }
            }

            if (!hasFoundOrg)
            {
                throw new Exception("Unable to match selected organization with list of organizations in this deployment");
            }
        }
    }

    /// <summary>
    /// The organization.
    /// </summary>
    public class Organization
    {
        /// <summary>
        /// Gets or sets the organization detail.
        /// </summary>
        public OrganizationDetail OrganizationDetail { get; set; }

        /// <summary>
        /// The to string.
        /// </summary>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public override string ToString()
        {
            return this.OrganizationDetail.FriendlyName;
        }
    }
}
